package top.guochenchen.mydb.backend.dm.pageCache;              // 包声明

import java.io.IOException;                                   // 导入IO异常
import java.io.RandomAccessFile;                              // 导入随机访问文件
import java.nio.ByteBuffer;                                   // 导入字节缓冲区
import java.nio.channels.FileChannel;                         // 导入文件通道
import java.util.concurrent.atomic.AtomicInteger;             // 导入原子整数
import java.util.concurrent.locks.Lock;                       // 导入锁接口
import java.util.concurrent.locks.ReentrantLock;              // 导入可重入锁

import top.guochenchen.mydb.backend.common.AbstractCache;       // 导入抽象缓存基类
import top.guochenchen.mydb.backend.dm.page.Page;               // 导入页面接口
import top.guochenchen.mydb.backend.dm.page.PageImpl;           // 导入页面实现
import top.guochenchen.mydb.backend.utils.Panic;                // 导入panic工具
import top.guochenchen.mydb.common.Error;                       // 导入错误定义

/**
 * 继承 AbstractCache 实现具体的页面缓存逻辑，管理数据库文件的页面读写、缓存和持久化。
 */
public class PageCacheImpl extends AbstractCache<Page> implements PageCache { // 页面缓存实现类
    
    private static final int MEM_MIN_LIM = 10;                // 最小内存限制，至少缓存10个页面
    public static final String DB_SUFFIX = ".db";             // 数据库文件后缀

    private RandomAccessFile file;                            // 数据库文件的随机访问句柄
    private FileChannel fc;                                   // 数据库文件的NIO通道
    private Lock fileLock;                                    // 文件操作锁，保护文件读写

    private AtomicInteger pageNumbers;                        // 原子整数，记录当前数据库的页面总数

    PageCacheImpl(RandomAccessFile file, FileChannel fileChannel, int maxResource) { // 构造函数
        super(maxResource);                                   // 调用父类构造函数，设置最大缓存页面数
        if(maxResource < MEM_MIN_LIM) {                       // 检查缓存容量是否过小
            Panic.panic(Error.MemTooSmallException);          // 过小则panic
        }
        long length = 0;                                      // 初始化文件长度
        try {
            length = file.length();                           // 获取数据库文件长度
        } catch (IOException e) {                             // 捕获IO异常
            Panic.panic(e);                                   // panic处理
        }
        this.file = file;                                     // 保存文件句柄
        this.fc = fileChannel;                                // 保存文件通道
        this.fileLock = new ReentrantLock();                  // 创建文件操作锁
        this.pageNumbers = new AtomicInteger((int)length / PAGE_SIZE); // 根据文件长度计算页面数
    }

    public int newPage(byte[] initData) {                     // 创建新页面方法
        int pgno = pageNumbers.incrementAndGet();             // 原子递增页面号
        Page pg = new PageImpl(pgno, initData, null);         // 创建新页面对象
        flush(pg);                                            // 立即刷新到磁盘
        return pgno;                                          // 返回新页面号
    }

    public Page getPage(int pgno) throws Exception {          // 根据页面号获取页面
        return get((long)pgno);                               // 调用父类get方法
    }

    /**
     * 页面获取流程。根据pageNumber从数据库文件中读取页数据，并包裹成Page
     */
    @Override
    protected Page getForCache(long key) throws Exception {   // 实现父类抽象方法：从数据源获取页面
        int pgno = (int)key;                                  // 将key转换为页面号
        long offset = PageCacheImpl.pageOffset(pgno);         // 计算页面在文件中的偏移

        ByteBuffer buf = ByteBuffer.allocate(PAGE_SIZE);      // 分配页面大小的缓冲区
        fileLock.lock();                                      // 加文件锁
        try {
            fc.position(offset);                              // 定位到页面偏移
            fc.read(buf);                                     // 读取页面数据到缓冲区
        } catch(IOException e) {                              // 捕获IO异常
            Panic.panic(e);                                   // panic处理
        }
        fileLock.unlock();                                    // 释放文件锁
        return new PageImpl(pgno, buf.array(), this);         // 创建页面对象并返回
    }
/**
 * 页面释放流程。页面被驱逐时的写回行为
 */
    @Override
    protected void releaseForCache(Page pg) {                 // 实现父类抽象方法：页面被驱逐时的写回
        if(pg.isDirty()) {                                    // 检查页面是否被修改
            flush(pg);                                        // 刷新到磁盘
            pg.setDirty(false);                               // 清除脏标记
        }
    }

    public void release(Page page) {                          // 释放页面引用
        release((long)page.getPageNumber());                  // 调用父类release方法
    }

    public void flushPage(Page pg) {                          // 强制刷新页面到磁盘
        flush(pg);                                            // 调用内部flush方法
    }

    private void flush(Page pg) {                             // 内部刷新方法
        int pgno = pg.getPageNumber();                        // 获取页面号
        long offset = pageOffset(pgno);                       // 计算页面偏移

        fileLock.lock();                                      // 加文件锁
        try {
            ByteBuffer buf = ByteBuffer.wrap(pg.getData());   // 包装页面数据为缓冲区
            fc.position(offset);                              // 定位到页面偏移
            fc.write(buf);                                    // 写入页面数据
            fc.force(false);                                  // 强制刷盘
        } catch(IOException e) {                              // 捕获IO异常
            Panic.panic(e);                                   // panic处理
        } finally {                                           // 确保锁被释放
            fileLock.unlock();                                // 释放文件锁
        }
    }

    public void truncateByBgno(int maxPgno) {                 // 截断数据库文件到指定页面号
        long size = pageOffset(maxPgno + 1);                  // 计算截断后的文件大小
        try {
            file.setLength(size);                             // 设置文件长度
        } catch (IOException e) {                             // 捕获IO异常
            Panic.panic(e);                                   // panic处理
        }
        pageNumbers.set(maxPgno);                             // 更新页面总数
    }

    @Override
    public void close() {                                     // 关闭页面缓存
        super.close();                                        // 调用父类close，写回所有缓存页面
        try {
            fc.close();                                       // 关闭文件通道
            file.close();                                     // 关闭文件
        } catch (IOException e) {                             // 捕获IO异常
            Panic.panic(e);                                   // panic处理
        }
    }

    public int getPageNumber() {                              // 获取当前页面总数
        return pageNumbers.intValue();                        // 返回原子整数的值
    }

    private static long pageOffset(int pgno) {                // 计算页面在文件中的偏移
        return (pgno-1) * PAGE_SIZE;                          // 页面号从1开始，所以减1
    }
    
}